package com.ozo.easy.common.exception;

import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
import cn.dev33.satoken.exception.SaTokenException;
import com.ozo.easy.common.model.ResultVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import java.util.List;
import java.util.Set;

/**
 * 全局错误处理器
 *
 * @author ozo
 * @date 2024-12-26
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionAdvice {

    /**
     * 请求参数缺失
     *
     * <p>
     * 接口上设置了@RequestParam("xx")参数，结果并未传递xx
     */
    @ExceptionHandler(value = MissingServletRequestParameterException.class)
    public ResultVO handleMissingServletRequestParameterException(MissingServletRequestParameterException ex) {
        log.error("[MissingServletRequestParameterException]", ex);

        return ResultVO.fail(CommonExceptionEnum.BAD_REQUEST.getCode(), String.format("请求参数缺失:%s", ex.getParameterName()));
    }

    /**
     * 请求参数类型错误
     *
     * <p>
     * 接口上设置了@RequestParam("xx")参数为Integer，结果传递xx类型为String
     */
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public ResultVO handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException ex) {
        log.error("[MethodArgumentTypeMismatchException]", ex);

        return ResultVO.fail(CommonExceptionEnum.BAD_REQUEST.getCode(), String.format("请求参数类型错误:%s", ex.getMessage()));
    }

    /**
     * 参数校验异常
     */
    @ExceptionHandler({MethodArgumentNotValidException.class, BindException.class})
    public ResultVO handleMethodArgumentNotValidException(Exception ex) {
        log.error("[MethodArgumentNotValidException]", ex);

        List<FieldError> fieldErrors = null;
        // 参数校验不正确
        if (ex instanceof MethodArgumentNotValidException) {
            fieldErrors = ((MethodArgumentNotValidException) ex).getBindingResult().getFieldErrors();
        }
        // 参数绑定不正确
        if (ex instanceof BindException) {
            fieldErrors = ((BindException) ex).getBindingResult().getFieldErrors();
        }

        if (fieldErrors == null) {
            return ResultVO.fail(CommonExceptionEnum.BAD_REQUEST);
        }

        StringBuilder stringBuilder = new StringBuilder();
        for (FieldError fieldError : fieldErrors) {
            stringBuilder.append(fieldError.getField() + ":" + fieldError.getDefaultMessage()).append(";");
        }
        return ResultVO.fail(CommonExceptionEnum.BAD_REQUEST, stringBuilder.toString());
    }

    /**
     * 参数校验异常
     */
    @ExceptionHandler({ConstraintViolationException.class, ValidationException.class})
    public ResultVO handleConstraintViolationException(Exception ex) {
        log.error("[ConstraintViolationException]", ex);

        // Validator校验不通过产生的异常
        StringBuilder stringBuilder = new StringBuilder();
        if (ex instanceof ConstraintViolationException) {
            Set<ConstraintViolation<?>> constraintViolations = ((ConstraintViolationException) ex).getConstraintViolations();
            for (ConstraintViolation<?> constraintViolation : constraintViolations) {
                stringBuilder.append(constraintViolation.getMessage()).append(";");
            }
        }
        // 本地参数校验时，抛出的ValidationException异常
        if (ex instanceof ValidationException) {
            stringBuilder.append(ex.getMessage());
        }

        return ResultVO.fail(CommonExceptionEnum.BAD_REQUEST, stringBuilder.toString());
    }

    /**
     * 请求方法不正确
     *
     * <p>
     * 接口的方法为GET方式，结果请求方法为POST方式，导致不匹配
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public ResultVO handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException ex) {
        log.warn("[HttpRequestMethodNotSupportedException]", ex);

        return ResultVO.fail(CommonExceptionEnum.METHOD_NOT_ALLOWED);
    }

    /**
     * 数据类型异常
     */
    @ExceptionHandler({HttpMessageNotReadableException.class})
    public ResultVO handleMethodArgumentNotValidException(HttpMessageNotReadableException ex) {
        log.error("[HttpMessageNotReadableException]", ex);

        return ResultVO.fail(CommonExceptionEnum.HTTP_MESSAGE_NOT_READABLE);
    }

    /**
     * 业务异常
     */
    @ExceptionHandler(ServiceException.class)
    public ResultVO handleServiceException(ServiceException ex) {
        log.error("[ServiceException]", ex);

        return ResultVO.fail(ex.getCode(), ex.getErrorMessage());
    }

    /**
     * 未登录异常
     */
    @ExceptionHandler(NotLoginException.class)
    public ModelAndView handleNotLoginException(NotLoginException ex) {
        log.error("[NotLoginException]", ex);

        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("exception", ex);
        modelAndView.setViewName("redirect:/login");
        return modelAndView;
    }

    /**
     * 无权限异常
     */
    @ExceptionHandler({NotRoleException.class, NotPermissionException.class})
    public ResultVO handleSaTokenException(SaTokenException ex) {
        log.error("[SaTokenException]", ex);

        return ResultVO.fail(CommonExceptionEnum.FORBIDDEN, ex.getMessage());
    }

    /**
     * 404错误
     */
    @ExceptionHandler(NoHandlerFoundException.class)
    public ResultVO handle404(NoHandlerFoundException ex) {
        log.error("[NoHandlerFoundException]", ex);

        return ResultVO.fail(CommonExceptionEnum.NOT_FOUND);
    }

    /**
     * 兜底异常
     */
    @ExceptionHandler(Exception.class)
    public ResultVO handleException(Exception ex) {
        log.error("[Exception]", ex);

        return ResultVO.fail(CommonExceptionEnum.INTERNAL_SERVER_ERROR);
    }

}
